/* Create chart boxes from object map */

#include <cassert>
#include <cstddef>
#include <exception>

#include <sigc++/sigc++.h>

#include <glibmm/i18n.h>

#include <gdkmm/color.h>

#include <gtkmm/alignment.h>
#include <gtkmm/dialog.h>
#include <gtkmm/entry.h>
#include <gtkmm/enums.h>
#include <gtkmm/eventbox.h>
#include <gtkmm/frame.h>
#include <gtkmm/messagedialog.h>
#include <gtkmm/table.h>
#include <gtkmm/label.h>
#include <gtkmm/separator.h>
#include <gtkmm/stock.h>
#include <gtkmm/window.h>

#include <memchart/class_info.hpp>
#include <memchart/exception.hpp>
#include <memchart/types.hpp>

#include "chart_box.hpp"
#include "object_map.hpp"

#include "memory_chart.hpp"

namespace memchart
{
	static const size_t max_width_chars = 16;
	static const unsigned border_width = 2;

	void MemoryChart::update_boxes (const object_map & objects)
	{
		make_boxes_unseen();

		/* Iterate through each object... */
		for (object_map::const_iterator it = objects.begin();
			it != objects.end(); ++it)
		{
			/* ...and create a Gtk::Table with all object's members */

			uintptr_t addr = it->first;
			const class_info & info = *(it->second);

			box_iter box_it = boxes.find(addr);
			if (box_it != boxes.end() && box_it->second.type_name != info.name)
			{
				/*
				 * The address is the same, but the object is of different
				 * type
				 */

				boxes.erase(box_it);
				box_it = boxes.end();
			}
			if (box_it == boxes.end())
			{
				chart_box & box = boxes[addr];
				box.type_name = info.name;
				box.edges.resize(info.count() - info.non_pointer_count());

				Gtk::Frame *frame = new Gtk::Frame;
				box.widget = shared_ptr<Gtk::Widget>(frame);
				frame->modify_bg(Gtk::STATE_NORMAL, Gdk::Color("black"));
				add(*frame);

				Gtk::Table *table =
					new Gtk::Table(2 * info.non_pointer_count() + 1, 3);
				frame->add(*Gtk::manage(table));

				/* Create heading */
				Gtk::Alignment *name_align = new Gtk::Alignment;
				table->attach(*Gtk::manage(name_align), 0, 3, 0, 1);
				name_align->set_border_width(border_width);
				Gtk::Label *name_label = new Gtk::Label(info.name);
				name_align->add(*Gtk::manage(name_label));
				name_label->set_max_width_chars(max_width_chars * 2);
				name_label->set_tooltip_text(to_string(addr));

				/* Add members */
				for (size_t i = 0; i < info.count(); ++i)
				{
					if (!info[i].is_pointer())
					{
						table->attach(*Gtk::manage(new Gtk::HSeparator),
							0, 3, 2 * i + 1, 2 * i + 2);

						Gtk::Label *label;
						Gtk::Alignment *align;

						align = new Gtk::Alignment(
							Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, 0, 1);
						table->attach(*Gtk::manage(align),
							0, 1, 2 * i + 2, 2 * i + 3);
						align->set_border_width(border_width);
						label = new Gtk::Label(info[i].name);
						align->add(*Gtk::manage(label));
						label->set_max_width_chars(max_width_chars);
						label->set_tooltip_text(info[i].type +
							" " + info[i].name);

						table->attach(*Gtk::manage(new Gtk::VSeparator),
							1, 2, 2 * i + 2, 2 * i + 3);

						align = new Gtk::Alignment(
							Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, 0, 1);
						table->attach(*Gtk::manage(align),
							2, 3, 2 * i + 2, 2 * i + 3);
						align->set_border_width(border_width);
						Gtk::EventBox *evbox = new Gtk::EventBox;
						align->add(*Gtk::manage(evbox));
						label = new Gtk::Label;
						evbox->add(*Gtk::manage(label));
						evbox->modify_bg(Gtk::STATE_NORMAL,
							Gdk::Color("white"));
						evbox->modify_bg(Gtk::STATE_INSENSITIVE,
							Gdk::Color("white"));
						label->set_max_width_chars(max_width_chars);
						evbox->signal_button_press_event().connect(
							sigc::bind(
								sigc::mem_fun(*this,
									&MemoryChart::on_value_change),
								&info[i], addr, label
								));

						/* Save label for later access */
						box.members.push_back(label);
					}
				}
			}

			/* Fill values */
			chart_box & box = boxes[addr];
			box.seen = true;
			vector<Gtk::Label *>::iterator m = box.members.begin();
			edge_iter e = box.edges.begin();
			for (size_t i = 0; i < info.count(); ++i)
			{
				if (!info[i].is_pointer())
				{
					(*m)->set_text(info[i].read(addr));
					(*m)->set_tooltip_text(info[i].read(addr));
					++m;
				}
				else
				{
					uintptr_t p = info[i].read_pointer(addr);
					/* TODO */
					*e++ = p != addr ? p : 0;
				}
			}
		}

		/* Delete old boxes */
		for (box_iter it = boxes.begin(); it != boxes.end(); )
		{
			if (!it->second.seen)
				boxes.erase(it++);
			else
				++it;
		}
	}
	
	bool MemoryChart::on_value_change (GdkEventButton *event,
		const member_info *m, uintptr_t obj, Gtk::Label *label)
	{
		if (event->type == GDK_2BUTTON_PRESS)
		{
			if (m->is_writable())
			{
				Gtk::Window *top = dynamic_cast<Gtk::Window *>(get_toplevel());
				assert(top != NULL);
			
				Gtk::Dialog dialog(_("Change value"), *top, true);
				Gtk::Entry entry;
				entry.set_text(m->read(obj));
				entry.set_activates_default();
				dialog.get_vbox()->pack_start(entry);
				dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
				dialog.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
				dialog.set_default_response(Gtk::RESPONSE_OK);
				dialog.show_all_children();
				
				if (dialog.run() == Gtk::RESPONSE_OK)
				{
					try
					{
						m->write(obj, entry.get_text());
					}
					catch (const std::exception & e)
					{
						Gtk::MessageDialog msg(*top,
							_("Could not change the value"), false,
							Gtk::MESSAGE_ERROR);
						msg.set_secondary_text(e.what());
						msg.run();
					}
					label->set_text(m->read(obj));
					label->set_tooltip_text(m->read(obj));
					get_bin_window()->invalidate(false);
				}
			}
			else
				signal_status_message.emit(_("This value cannot be changed"));
			
			return true;
		}
		else
			return false;
	}
}
